﻿//!CompilerOption:Optimize:On
// the above line is a flag that turns on optimization.. leave it as it is

// ********************* Highvoltz's Premium Honorbuddy Plugin Template **********************

// if the project files are not in HB's folder you can have the Post Build events automatically copy the files over
// to the Honorbuddy folder. To set it up go to Project -> Project Properties -> Build Events -> Edit Post Build
// and remove the 'rem' at the front of the line and change the 2nd string to the path you want to copy the files to.
// can be relative or absolute path. If you have a dropbox account you can also have it copy directly to your Dropbox folder
// xcopy "$(ProjectDir)*.cs" "$(ProjectDir)..\..\Honorbuddy\Plugins\TestPLGIN" /I /S /Y

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Windows.Forms;
using System.Diagnostics;
using System.IO;
using RoutesAutomator;
using Styx;
using Styx.Helpers;
using Styx.Logic;
using Styx.Logic.AreaManagement;
using Styx.Logic.BehaviorTree;
using Styx.Logic.Combat;
using Styx.Logic.Inventory.Frames.Gossip;
using Styx.Logic.Inventory.Frames.LootFrame;
using Styx.Logic.Pathing;
using Styx.Logic.Profiles;
using Styx.Plugins;
using System.Text;
using Styx.Plugins.PluginClass;
using Styx.WoWInternals;
using Styx.WoWInternals.World;
using Styx.WoWInternals.WoWObjects;
using Styx.Patchables;
using System.Collections.Specialized;
using Tripper.Tools.Math;
using System.Collections;

namespace RoutesAutomator
{
    class RoutesAutomator : HBPlugin, IDisposable
    {
        Hashtable _MapsCache = new Hashtable(); 
        Hashtable _WorldMapArea = new Hashtable();

        private int _counter=0;
        public override string Name { get { return Constants.PluginName; } }
        public override string Author { get { return Constants.Author; } }
        public override Version Version { get { return Constants.Version; } }
        public override string ButtonText { get { return Constants.PluginName; } }
        public override bool WantButton { get { return true; } }

        public static RoutesAutomator Instance { get; private set; }
        public static System.Random Rng = new Random();
        public event EventHandler OnImportDone;

        public RoutesBuddySettings MySettings;
        LocalPlayer Me = ObjectManager.Me;
        MainForm Gui;
        public List<Route> Routes;
        public List<List<string>> RawImport;
        enum Height { High, Low };
        public RoutesAutomator() { Instance = this; }
        // put your initialization code in here..
        public override void Initialize()
        {
            MySettings = new RoutesBuddySettings
                (Path.Combine(Logging.ApplicationPath, string.Format(@"Settings\{0}\{0}.xml", Name)));
            if (string.IsNullOrEmpty(MySettings.LastFolder))
                MySettings.LastFolder = Environment.CurrentDirectory;
            Gui = new MainForm();
        }
        public override void OnButtonPress()
        {
            Gui.Show();
        }
        // Abort any created threads or unhook any events here 
        public override void Dispose()
        {
            //if (Gui!=null) Gui.Dispose();
        }
        // this gets called on every plugin pulse.. like 13 times per sec, put your main code here
        public override void Pulse()
        {
        }

        public void ImportRoutes()
        {
            int crashLocation = 0;
            try
            {

                Routes = new List<Route>();
                string table = RandomString;
                using (new FrameLock())
                {
                    crashLocation = 1;
                    string lua1 =
                        string.Format(
                            "if RoutesDB then {0} = {{}} for k,v in pairs(RoutesDB.global.routes) do for k1,v1 in pairs (v) do if #v1.route > 0 then table.insert({0},{{k,k1,unpack(v1.route)}}) end end end return #{0} end return 0 ",
                            table);
                    crashLocation = 2;
                    int routeNum = Lua.GetReturnVal<int>(lua1, 0);
                    if (routeNum == 0)
                    {
                        Utility.Write(System.Drawing.Color.Red, "No Routes found");
                        return;
                    }
                    RawImport = new List<List<string>>();
                    crashLocation = 3;
                    for (int i = 1; i <= routeNum; i++)
                    {
                        crashLocation = 4;
                        List<string> retVal = Lua.GetReturnValues(string.Format("return unpack({0}[{1}])", table, i));
                        crashLocation = 5;
                        if (retVal != null && retVal.Count >= 3)
                        {
                            crashLocation = 6;
                            RawImport.Add(retVal);
                        }
                    }
                    crashLocation = 7;
                    Lua.DoString(table + " = {}");
                }

                crashLocation = 8;
                uint myContinent = ObjectManager.Me.MapId;
                for (int i = 0; i < RawImport.Count; i++)
                {
                    crashLocation = 9;
                    uint mapId = FindMapId(RawImport[i][0]);
                    crashLocation = 10;
                    uint continentId = GetContinentId(mapId);
                    crashLocation = 11;
                    if (myContinent != continentId)
                    {
                        //Utility.Write("Skipping {0} because its on a different continent", RawImport[i][1]);
                    }
                    else
                    {
                        if (mapId > 0)
                        {
                            crashLocation = 12;
                            List<WoWPoint> highPoints = RouteToWoWPoint(RawImport[i], mapId, Height.High, i);
                            crashLocation = 13;
                            List<WoWPoint> lowPoints = RouteToWoWPoint(RawImport[i], mapId, Height.Low, i);

                            crashLocation = 14;
                            if (highPoints.Count > 0 && lowPoints.Count > 0)
                                Routes.Add(new Route(mapId, string.Format("{0}-{1}", RawImport[i][0], RawImport[i][1]),
                                                     ProcessPoints(highPoints, Height.High),
                                                     ProcessPoints(lowPoints, Height.Low)));
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Utility.WriteDebug(System.Drawing.Color.Red, "Crash Code : " + crashLocation + " : " + ex);
            }
            if (OnImportDone != null)
                OnImportDone(Instance, null);
        }

        List<WoWPoint> RouteToWoWPoint(List<string> rawPoints, uint mapId, Height ht, int status)
        {
            var points = new List<WoWPoint>();

            for (var n = 2; n < rawPoints.Count; n++)
            {
                uint coord;
                uint.TryParse(rawPoints[n], out coord);
                //local ex, ey = floor(point / 10000) / 10000, (point % 10000) / 10000
                float x = (float) Math.Floor((float) coord/10000f)/10000f;
                float y = (coord%10000f)/10000f;
                points.Add(mapToWorldCoords(x, y, mapId, ht));
                if (!Gui.IsVisible) continue;
                int retCnt = RawImport.Count;
                int process1 = (status*100)/retCnt;
                int process2 = ((status + 1)*100)/retCnt;
                int process3 = (n*(process2 - process1)/rawPoints.Count) + process1;
                Gui.UpdateProgressBar(process3);
            }
            return points;

            //var points = new List<WoWPoint>();
            //foreach (var point in rawPoints)
            //{
            //    uint coord;
            //    uint.TryParse(point, out coord);
            //    //local ex, ey = floor(point / 10000) / 10000, (point % 10000) / 10000
            //    float x = (float)Math.Floor(coord / 10000f) / 10000f;
            //    float y = (coord % 10000f) / 10000f;
            //    points.Add(mapToWorldCoords(x, y, mapId, ht));
            //}

            //for (int n = 2; n < rawPoints.Count; n++)
            //{
            //    float x, y;
            //    uint coord;
            //    uint.TryParse(rawPoints[n], out coord);
            //    //local ex, ey = floor(point / 10000) / 10000, (point % 10000) / 10000
            //    x = (float)Math.Floor((float)coord / 10000f) / 10000f;
            //    y = ((float)coord % 10000f) / 10000f;
            //    points.Add(mapToWorldCoords(x, y, mapId,ht));
            //    int retCnt = RawImport.Count;
            //    int process1 = (status * 100) / retCnt;
            //    int process2 = ((status + 1) * 100) / retCnt;
            //    int process3 = (n * (process2 - process1) / rawPoints.Count) + process1;
            //    //Gui.UpdateProgressBar(process3);
            //}
            //return points;
        }

        List<WoWPoint> ProcessPoints(List<WoWPoint> points, Height ht)
        {
            List<WoWPoint> newPoints = new List<WoWPoint>();
            newPoints.Add(points[0]);
            for (int i = 1; i < points.Count; i++)
            {
                List<WoWPoint> tempPoints = CreatePathSegment(points[i - 1], points[i], ht);
                if (Gui.smoothCheck.Checked)
                    tempPoints = SmoothOut3dSegment(tempPoints);
                newPoints.AddRange(tempPoints);
            }
            return newPoints;
        }

        List<WoWPoint> CreatePathSegment(WoWPoint from, WoWPoint to, Height ht)
        {
            List<WoWPoint> segment = new List<WoWPoint>();
            WoWPoint point = from;
            float step = 50;
            float noMeshStep = 5;
            for (float i = from.Distance(to) - step; i > 0; )
            {
                point = WoWMathHelper.CalculatePointFrom(from, to, i);
                try
                {
                    float z = ht == Height.High? Navigator.FindHeights(point.X, point.Y).Max():
                        Navigator.FindHeights(point.X, point.Y).Min();
                    i -= step;
                    if (Gui.smoothCheck.Checked && z > point.Z)
                        point.Z = z;
                    segment.Add(point);
                }
                catch { i -= noMeshStep; }
            }
            segment.Add(to);
            return segment;
        }

        List<WoWPoint> SmoothOut3dSegment(List<WoWPoint> path)
        {
            WoWPoint end = path[path.Count - 1];
            int startI = 0;
            int maxIndex = startI;
            while (startI < path.Count - 1)
            {
                WoWPoint endV = path[startI].GetDirectionTo(end);
                float highestZ = endV.Z;
                for (int i = startI; i < path.Count; i++)
                {
                    WoWPoint pointV = path[startI].GetDirectionTo(path[i]);
                    if (pointV.Z >= highestZ)
                    {
                        highestZ = pointV.Z;
                        maxIndex = i;
                    }
                }
                if (maxIndex == startI)
                    maxIndex = path.Count - 1;
                float multiplier = (path[maxIndex].Z - path[startI].Z) / (path[startI].Distance2D(path[maxIndex]));
                for (int n = startI + 1; n < maxIndex; n++)
                {
                    WoWPoint point = path[n];
                    point.Z = (path[startI].Distance2D(path[n]) * multiplier) + path[startI].Z;
                    path[n] = point;
                }
                startI = maxIndex;
            }
            return path;
        }

        WoWPoint mapToWorldCoords(float x, float y, uint mapId, Height ht)
        {
            var worldPoint = new WoWPoint();
            var worldMapAreaFields = 
                _WorldMapArea[mapId] as WoWDb.Row ??
                StyxWoW.Db[ClientDb.WorldMapArea].GetRow(mapId);

            var ay = worldMapAreaFields.GetField<float>(4);
            var by = worldMapAreaFields.GetField<float>(5);
            var ax = worldMapAreaFields.GetField<float>(6);
            var bx = worldMapAreaFields.GetField<float>(7);
            worldPoint.X = ax + (y * (bx - ax));
            worldPoint.Y = ay + (x * (by - ay));
            try
            {
                worldPoint.Z = ht == Height.High ? Navigator.FindHeights(worldPoint.X, worldPoint.Y).Max():
                    Navigator.FindHeights(worldPoint.X, worldPoint.Y).Min();
            }
            catch { return TryGetHieght(worldPoint,ht); }
            return worldPoint;
        }

        WoWPoint TryGetHieght(WoWPoint point,Height ht)
        {
            float PIx2 = (float)Math.PI * 2f;
            int step = 20;
            for (int d = 5; d <= 50; d += 5)
            {
                for (int i = 0; i < 20; i++)
                {
                    WoWPoint newPoint = point.RayCast((i * PIx2) / step, d);
                    try
                    {
                        newPoint.Z = ht == Height.High ? Navigator.FindHeights(newPoint.X, newPoint.Y).Max() :
                            Navigator.FindHeights(newPoint.X, newPoint.Y).Min();
                        return newPoint;
                    }
                    catch { }
                }
            }
            return point;
        }

        uint FindMapId(string localMapName)
        {
            uint mapId = 0;
            if (_MapsCache[localMapName]==null)
            {
                _counter++;
                Utility.WriteDebug("FindMapID Called : " + _counter);
                WoWDb.DbTable t = StyxWoW.Db[ClientDb.WorldMapArea];
                int max = t.MaxIndex;
                for (int i = t.MinIndex; i <= max; i++)
                {
                    WoWDb.Row r = t.GetRow((uint)i);
                    if (r == null || ObjectManager.Wow.Read<string>(r.GetField<uint>(3)) != localMapName) continue;
                    mapId = (uint) i;
                    break;
                }
                _MapsCache.Add(localMapName, mapId);
            }
            else
            {
                mapId = uint.Parse(_MapsCache[localMapName].ToString());
            }

            return mapId;
        }

        uint GetContinentId(uint mapID)
        {
            WoWDb.DbTable t = StyxWoW.Db[ClientDb.WorldMapArea];
            WoWDb.Row r = t.GetRow(mapID);
            return r.GetField<uint>(1);
        }

        public static string RandomString
        {
            get
            {
                int size = Rng.Next(6, 15);
                var sb = new StringBuilder(size);
                for (int i = 0; i < size; i++)
                {
                    // random upper/lowercase character using ascii code
                    sb.Append((char)(Rng.Next(2) == 1 ? Rng.Next(65, 91) + 32 : Rng.Next(65, 91)));
                }
                return sb.ToString();
            }
        }
    }
}
